package edu.cuit.wedding.commons.servlet;

import com.alibaba.fastjson2.JSONObject;
import edu.cuit.wedding.commons.annotation.Controller;
import edu.cuit.wedding.commons.annotation.RequestMapping;
import edu.cuit.wedding.commons.annotation.ResponseBody;
import edu.cuit.wedding.commons.exception.InitException;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URISyntaxException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 模拟SpringMVC的前端控制器
 *  客户端发送请求到这个DispatcherServlet前端控制器
 */
@Slf4j
public class DispatcherServlet extends HttpServlet {
    //处理器映射器（请求的url和方法的对应关系）
    private static final Map<String, Method> handlerMapping = new HashMap<>();
    //处理器适配器（为处理器-方法）准备执行环境---该方法所对应的对象实例
    private static final Map<Method,Object> instancePools = new HashMap<>();

    /**
     * servlet生命周期方法：初始化方法，该方法在创建该servlet实例后调用(只初始化一次)
     * 1.处理器映射器（请求的url和方法的对应关系）的初始化
     * 2.处理器适配器的初始化
     * @throws ServletException
     */
    @Override
    public void init() throws ServletException {
        try {
            //初始化参数:前端控制器要扫描的控制所在的包
            var basePackages = getInitParameter("basePackages");
            var root = getServletContext().getContextPath();
            //处理器映射器和url的映射
            mappingProcess(root,basePackages);
        } catch (IOException | URISyntaxException e) {
            log.error(e.getMessage(),e);
        }
    }

    /**
     * 处理器适配器
     *      1、处理请求uri和Method之间的映射关系
     *      2、映射Method和其对应的实例
     *
     *@param root root根路径
     *          uri: "/" + @RequestMapping("/control/book/") + @RequestMapping("/save")
     *          ==> /control/book/save
     */
    private void mappingProcess(String root,String basePackages) throws IOException, URISyntaxException {
        //1获取basePackages包下的所有的Controller类
        var classes = packageScan(basePackages);
        for(var clazz : classes){
            //获取所有标注了@Controller注解的class
            var controller = clazz.getAnnotation(Controller.class);
            if(controller != null){//表明该class是一个控制器

                try {
                    var path = root;
                    //创建该控制器的实例
                    var instance = clazz.getDeclaredConstructor().newInstance();
                    var mapping = clazz.getAnnotation(RequestMapping.class);
                    if(mapping != null){
                        var prefix = mapping.value().startsWith("/") ? mapping.value() : "/" + mapping.value();
                        path += prefix.endsWith("/") ? prefix : prefix + "/";
                    }
                    //获取该类下所有的方法
                    var methods = clazz.getMethods();
                    for(var method : methods){
                        var annotation = method.getAnnotation(RequestMapping.class);
                        if(annotation != null){
                            //计算出url
                            var suffix = annotation.value().startsWith("/") ? annotation.value().substring(1) : annotation.value();
                            var url = path + suffix;
                            if(handlerMapping.containsKey(url)){ //重复的url
                                log.error("url冲突:" + url);
                                throw new InitException("url冲突:" + url);
                            }
                            handlerMapping.put(url,method);
                            instancePools.put(method,instance);
                        }
                    }
                } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
                    log.error(e.getMessage(),e);
                }
            }
        }
    }

    /**
     * 扫描控制层，得到控制层所有java文件的Class对象
     * 使用FileVisitor遍历文件和目录（非递归）
     *
     * @param basePackages 包信息，扫描该包及其子包下的java文件
     * @return 所有java文件的Class对象
     *
     */
    private List<Class<?>> packageScan(String basePackages) throws IOException, URISyntaxException {
        var classes = new ArrayList<Class<?>>();

        var root = basePackages.replace(".","\\");
        var url = Thread.currentThread().getContextClassLoader().getResource(root);
        //walkFileTree(path):遍历path路径下所有的文件和子目录
        Files.walkFileTree(Paths.get(url.toURI()),new SimpleFileVisitor<Path>(){
            @Override //访问到文件后触发该方法
            public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) throws IOException {
                try {
                    var filePath = path.toString();
                    if (filePath.endsWith(".class")){//是一个class字节码文件，得到java文件的全限定名称
                        var className = filePath.substring(filePath.indexOf(root) ,filePath.length() - 6).replace("\\",".");
                        classes.add(Class.forName(className));
                    }
                } catch (Exception e) {
                    log.error(e.getMessage(),e);
                }
                return FileVisitResult.CONTINUE; //继续处理文件
            }
        });
        return classes;
    }


    /**
     * 处理get请求
     */
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        forward(request, response);
    }

    /**
     * 处理post请求
     */
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        forward(request, response);
    }

    /**
     * 处理put请求
     */
    @Override
    protected void doPut(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        forward(request, response);
    }

    /**
     * 处理get请求
     */
    @Override
    protected void doDelete(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        forward(request, response);
    }


    /**
     * 分发请求
     * @param request HttpServletRequest
     * @param response HttpServletResponse
     * @throws ServletException
     * @throws IOException
     */
    public void forward(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        try {
            //获取请求的uri
            var uri = request.getRequestURI();
            var method = handlerMapping.get(uri);
            if(method != null){//执行该方法
                var instance = instancePools.get(method);
                var result = method.invoke(instance,request,response);
                if(result != null){//对返回值做出处理
                    //如果有@ResponseBody注解，直接将内容进行输出json
                    if (method.getAnnotation(ResponseBody.class) != null)
                        responseJson(response,result);
                    else
                        resolverView(request,response,result.toString()); //处理逻辑视图
                }
            } else {
                var dispatcher = uri.endsWith(".jsp") || uri.equals(".jspx") ?
                        //获取tomcat处理jsp的servlet
                        getServletContext().getNamedDispatcher("jsp") :
                        //交给tomcat的默认Servlet进行处理
                        getServletContext().getNamedDispatcher("default");//获取tomcat的默认的servlet，处理静态资源

                dispatcher.forward(request,response);
            }
        } catch (IllegalAccessException | InvocationTargetException e) {
            log.error(e.getMessage(),e);
        }

    }

    /**
     * 向客户端输出json
     * @param response 响应对象
     * @param value 将该value以json格式输出
     * @throws IOException
     */
    private void responseJson(HttpServletResponse response, Object value) throws IOException {
        //response.setCharacterEncoding("UTF-8");
        var writer = response.getWriter();
        response.setContentType("application/json; charset=utf-8");
        writer.write(JSONObject.toJSONString(value));

    }

    /**
     * 解析视图
     *  规则约定：1、视图默认存放于/pages/下
     *          2、视图默认为jsp
     *          3、控制层默认以分发的形式寻找渲染视图
     *          4、重定向约定：redirect:/url
     */
    private void resolverView(HttpServletRequest request,HttpServletResponse response,String viewName) throws IOException, ServletException {
        if(viewName.startsWith("redirect:")){//需要重定向
            var view = viewName.substring("redirect:".length());
            response.sendRedirect(getServletContext().getContextPath() + view);
        } else if(viewName.startsWith("forward:")){//分发到指定的视图
            var view = viewName.substring("forward:".length());
            request.getRequestDispatcher(view).forward(request,response);
        } else {// 视图分发： 默认规则
            var prefix = "/pages/";
            var suffix = ".jsp";
            // /pages/message.jsp
            var view = prefix + viewName + suffix;
            request.getRequestDispatcher(view).forward(request,response);
        }
    }
}
